﻿#!/usr/bin/python
# -*- coding: utf-8 -*-

''' AST.py, version 0.2

Petit module utilitaire pour la construction, la manipulation et la 
représentation d'arbres syntaxiques abstraits.

Sûrement plein de bugs et autres surprises. À prendre comme un 
"work in progress"...
Notamment, l'utilisation de pydot pour représenter un arbre syntaxique cousu
est une utilisation un peu "limite" de graphviz. Ça marche, mais le layout n'est
pas toujours optimal...

2008-2009, Matthieu Amiguet, HE-Arc

!!! Modification !!! :
	> WhileNode -> LoopNode
	> Ajout d'une variable var au noeud ProgramNode
!!! Suppression !!! :
	> EntryNode
	> PrintNode
!!! Ajout !!!
	> ForNode
	> IfNode
	> ElifNode
	> ElseNode
	> SelectNode
	> CaseNode
	> BreakNode
	> FuncDefNode
	> FuncUseNode
	> ParamsUseNode
	> ReturnNode
	> StringNode
	> ParenthesesNode
	> ConfigDirNode
	> ConfigPullUpNode
	> ConfigUSARTNode
	> ConfigPWMT0Node
	> EnableNode
	> DisableNode
	> WriteNode
	> FlipNode
	> ReadNode
'''

import pydot

class Node:
	count = 0
	type = 'Node (unspecified)'
	shape = 'ellipse'
	def __init__(self,children=None):
		self.ID = str(Node.count)
		Node.count+=1
		if not children: self.children = []
		elif hasattr(children,'__len__'):
			self.children = children
		else:
			self.children = [children]
		self.next = []

	def addNext(self,next):
		self.next.append(next)

	def asciitree(self, prefix=''):
		result = "%s%s\n" % (prefix, repr(self))
		prefix += '|  '
		for c in self.children:
			if not isinstance(c,Node):
				result += "%s*** Error: Child of type %r: %r\n" % (prefix,type(c),c)
				continue
			result += c.asciitree(prefix)
		return result
	
	def __str__(self):
		return self.asciitree()
	
	def __repr__(self):
		return self.type
	
	def makegraphicaltree(self, dot=None, edgeLabels=True):
			if not dot: dot = pydot.Dot()
			dot.add_node(pydot.Node(self.ID,label=repr(self), shape=self.shape))
			label = edgeLabels and len(self.children)-1
			for i, c in enumerate(self.children):
				c.makegraphicaltree(dot, edgeLabels)
				edge = pydot.Edge(self.ID,c.ID)
				if label:
					edge.set_label(str(i))
				dot.add_edge(edge)
				#Workaround for a bug in pydot 1.0.2 on Windows:
				#dot.set_graphviz_executables({'dot': r'D:\Programmes\Extra\Graphviz2.26\bin\dot.exe'})
			return dot
		
	def threadTree(self, graph, seen = None, col=0):
			colors = ('red', 'green', 'blue', 'yellow', 'magenta', 'cyan')
			if not seen: seen = []
			if self in seen: return
			seen.append(self)
			new = not graph.get_node(self.ID)
			if new:
				graphnode = pydot.Node(self.ID,label=repr(self), shape=self.shape)
				graphnode.set_style('dotted')
				graph.add_node(graphnode)
			label = len(self.next)-1
			for i,c in enumerate(self.next):
				if not c: return
				col = (col + 1) % len(colors)
				color = colors[col]				   
				c.threadTree(graph, seen, col)
				edge = pydot.Edge(self.ID,c.ID)
				edge.set_color(color)
				edge.set_arrowsize('.5')
				# Les arrêtes correspondant aux coutures ne sont pas prises en compte
				# pour le layout du graphe. Ceci permet de garder l'arbre dans sa représentation
				# "standard", mais peut provoquer des surprises pour le trajet parfois un peu
				# tarabiscoté des coutures...
				# En commantant cette ligne, le layout sera bien meilleur, mais l'arbre nettement
				# moins reconnaissable.
				edge.set_constraint('false') 
				if label:
					edge.set_taillabel(str(i))
					edge.set_labelfontcolor(color)
				graph.add_edge(edge)
			return graph	
		
			
class ProgramNode(Node):
	def __init__(self,children):
		Node.__init__(self, children)
		self.vars=[]
		self.type = 'Program'

	def __repr__(self):
		return "%s%s" % (self.type," "+repr(self.vars) if len(self.vars)!=0 else "")
		
class TokenNode(Node):
	type = 'token'
	def __init__(self, tok):
		Node.__init__(self)
		self.tok = tok
		
	def __repr__(self):
		return repr(self.tok)
	
class OpNode(Node):
	def __init__(self, op, children):
		Node.__init__(self,children)
		self.op = op
		try:
			self.nbargs = len(children)
		except AttributeError:
			self.nbargs = 1
		
	def __repr__(self):
		return "%s (%s)" % (self.op, self.nbargs)
	
class AssignNode(Node):
	type = '='
	
class LoopNode(Node):
	type = 'loop'

# Noeuds ajoutés

class ForNode(Node):
	type = 'for'	
		
class IfNode(Node):
	type = 'if'
	
class ElifNode(Node):
	type = 'elif'
	
class ElseNode(Node):
	type = 'else'

class SelectNode(Node):
	type = 'select'
	
class CaseNode(Node):
	type = 'case'
	
class BreakNode(Node):
	type = 'break'

class FuncDefNode(Node):
	def __init__(self, funcName, funcParams, children):
		Node.__init__(self,children)
		self.type = "func"
		self.funcName = funcName
		self.funcParams = funcParams
	def __repr__(self):
		return "%s (%s)" % (self.funcName, self.funcParams)

class FuncUseNode(Node):
	def __init__(self, funcName, children):
		Node.__init__(self, children)
		self.funcName = funcName
	def __repr__(self):
		return "%s (%s)" % (self.funcName, self.children)

class ParamsUseNode(Node):
	type = 'paramsUse'

class ReturnNode(Node):
	type = 'return'
	
class StringNode(Node):
	def __init__(self, string):
		Node.__init__(self)
		self.type = "string"
		self.string = string
	def __repr__(self):
		return "%s" % (self.string)

class ParenthesesNode(Node):
	type = '(...)'
	
class EnableNode(Node):
	def __init__(self, vars):
		Node.__init__(self)
		self.type = 'enable'
		self.vars = vars
	def __repr__(self):
		return "%s - %s" % (self.type,self.vars)

class DisableNode(Node):
	def __init__(self, vars):
		Node.__init__(self)
		self.type = 'disable'
		self.vars = vars
	def __repr__(self):
		return "%s - %s" % (self.type,self.vars)

class FlipNode(Node):
	def __init__(self, vars):
		Node.__init__(self)
		self.type = 'flip'
		self.vars = vars
	def __repr__(self):
		return "%s - %s" % (self.type,self.vars)
		
class ReadNode(Node):
	def __init__(self, vars):
		Node.__init__(self)
		self.type = 'read'
		self.vars = vars
	def __repr__(self):
		return "%s - %s" % (self.type,self.vars)
		
class WriteNode(Node):
	def __init__(self, vars):
		Node.__init__(self)
		self.type = 'write'
		self.vars = vars
	def __repr__(self):
		return "%s - %s" % (self.type,self.vars)
		
#ATMEL

class ConfigDirNode(Node):
	def __init__(self, inout, pin):
		Node.__init__(self)
		self.type = 'configDir'
		self.inout = inout
		self.pin = pin
	def __repr__(self):
		return "%s - %s %s" % (self.type,self.inout, self.pin)
		
class ConfigPullUpNode(Node):
	def __init__(self, onoff, pin):
		Node.__init__(self)
		self.type = 'configPullUp'
		self.onoff = onoff
		self.pin = pin
	def __repr__(self):
		return "%s - %s %s" % (self.type,self.onoff, self.pin)

class ConfigUSARTNode(Node):
	def __init__(self, baudrate, dataBits ,parity, stopBits ):
		Node.__init__(self)
		self.type = 'configUSART'
		self.baudrate = baudrate
		self.dataBits = dataBits
		self.parity = parity
		self.stopBits = stopBits

	def __repr__(self):
		return "%s - %s %s %s %s" % (self.type, self.baudrate, self.dataBits, self.parity, self.stopBits)

class ConfigPWMT0Node(Node):
	def __init__(self, prescaler):
		Node.__init__(self)
		self.type = 'configPWMT0'
		self.prescaler = prescaler
	def __repr__(self):
		return "%s - %s" % (self.type,self.prescaler)
		
def addToClass(cls):
	''' Décorateur permettant d'ajouter la fonction décorée en tant que méthode
	à une classe.
	
	Permet d'implémenter une forme élémentaire de programmation orientée
	aspects en regroupant les méthodes de différentes classes implémentant
	une même fonctionnalité en un seul endroit.
	
	Attention, après utilisation de ce décorateur, la fonction décorée reste dans
	le namespace courant. Si cela dérange, on peut utiliser del pour la détruire.
	Je ne sais pas s'il existe un moyen d'éviter ce phénomène.
	'''
	def decorator(func):
		setattr(cls,func.__name__,func)
		return func
	return decorator